#include "UDPServer.h"

UDPServer* UDPServer::instance = NULL;

UDPServer::UDPServer() :
	port(atoi(Configuration::get_property("service_port").c_str())), buff_size(
			atoi(Configuration::get_property("buffer_size").c_str())) {
	this->running = false;
}

UDPServer* UDPServer::getInstance() {
	if (instance == NULL)
		instance = new UDPServer();
	return instance;
}

void UDPServer::run() {
	Log::verbose("Waiting for clients..");
	this->running = true;
	raw_socket_fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP));
	if (raw_socket_fd == -1) {
		Log::error("Cannot create RAW socket. Make sure you have root access");
		shutdown(1);
	}
	while (running)
		proccessRequest();
}

void* UDPServer::requestThreadRouter(void* cmh) {
	reinterpret_cast<ClientHandler*> (cmh)->proccess_requests();
	return NULL;
}

void* UDPServer::responseThreadRouter(void* _this) {
	reinterpret_cast<UDPServer*> (_this)->proccessResponse();
	return NULL;
}

void UDPServer::proccessRequest() {
	char* buffer = new char[buff_size];
	const int bytes_read = read(raw_socket_fd, buffer, buff_size);

	if (bytes_read > 0) {
		ip* ip_datagram = (ip*) (buffer + ETH_HLEN);
		if (ip_datagram->ip_p == IPPROTO_UDP) {
			udphdr* udp_segment = (udphdr*) (buffer + ETH_HLEN
					+ (ip_datagram->ip_hl << 2));
			if (ntohs(udp_segment->dest) == this->port) {
				ethhdr* ether_frame = (ethhdr*) (buffer);
				const int data_len = bytes_read - ETH_HLEN
						- (ip_datagram->ip_hl << 2) - sizeof(udphdr);
				const string& mac = macAddressString(ether_frame->h_source);

				Log::log_oss << "Device from "
						<< inet_ntoa(ip_datagram->ip_src) << ": " << mac;
				Log::verbose();

				string data_recv(buffer + bytes_read - data_len);
				data_recv.resize(data_len);

				clientHandler.push_request(mac, data_recv);
				clients[mac] = ip_datagram->ip_src;
				pthread_create(&response_thread, NULL, responseThreadRouter,
						(void*) this);
				pthread_create(&request_thread, NULL, requestThreadRouter,
						(void*) &clientHandler);
			}
		}
	} else
		Log::warning("Cannot read bytes from RAW socket");
	delete[] buffer;
}

void UDPServer::proccessResponse() {
	while (!clients.empty())
		if (clientHandler.hasResponses()) {
			const pair<string, string>& response = clientHandler.pop_response();
			if (response.second.empty())
				clients.erase(response.first);
			else {
				const int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
				sockaddr_in clientaddr;
				clientaddr.sin_family = AF_INET;
				clientaddr.sin_port = htons(port);
				clientaddr.sin_addr = clients[response.first];

				clients.erase(response.first);

				Log::verbose(string("Sending response to device: ")
						+ response.first);
				sendto(sockfd, response.second.c_str(),
						response.second.length(), 0, (sockaddr*) &clientaddr,
						sizeof(clientaddr));
				close(sockfd);
			}
		}
}

string UDPServer::macAddressString(unsigned char* mac) {
	std::ostringstream oss;
	for (int i = 0; i < ETH_ALEN; i++) {
		oss << std::hex << std::uppercase << (mac[i] >> 4);
		oss << std::hex << std::uppercase << (mac[i] & 0x0f);
	}
	return oss.str();
}

void UDPServer::shutdown(const int& signal_code) {
	running = false;
	close(raw_socket_fd);
	delete instance;
	Log::verbose("Shutting down");
	exit(signal_code);
}
